package cn.ticsmyc.tools.framework.ioc.reader;

import cn.ticsmyc.tools.framework.ioc.annotation.Autowired;
import cn.ticsmyc.tools.framework.ioc.annotation.Component;
import cn.ticsmyc.tools.framework.ioc.annotation.Scope;
import cn.ticsmyc.tools.framework.ioc.annotation.Value;
import cn.ticsmyc.tools.framework.ioc.entity.BeanDefinition;
import cn.ticsmyc.tools.framework.ioc.entity.BeanReference;
import cn.ticsmyc.tools.framework.ioc.entity.PropertyValue;
import cn.ticsmyc.tools.framework.ioc.io.Resource;
import cn.ticsmyc.tools.framework.ioc.io.ResourceLoader;
import cn.ticsmyc.tools.packageScan.PackageScaner;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Set;

/**
 * @author Ticsmyc
 * @date 2021-04-22 17:10
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 从resources中加载beanDefinition
     *
     * @param location
     * @throws Exception
     */
    @Override
    public void loadBeanDefinition(String location) throws Exception {
        Resource resource = this.getResourceLoader().getResource(location);
        doLoadBeanDefinitions(resource);
    }

    private void doLoadBeanDefinitions(Resource resource) throws Exception {
        InputStream inputStream = resource.getInputStream();
        //解析xml
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse(inputStream);
        //根据解析到的document，向容器中注册bean
        parseXmlConfigFile(document);
        inputStream.close();
    }

    /**
     * 根据解析后的xml数据，向容器中注册bean
     *
     * @param document
     */
    private void parseXmlConfigFile(Document document) throws ClassNotFoundException {
        Element root = document.getDocumentElement();
        processXmlConfigFile(root);
    }

    private void processXmlConfigFile(Element root) throws ClassNotFoundException {
        NodeList childNodes = root.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node item = childNodes.item(i);
            if(item instanceof Element) {
                Element element = (Element) item;
                if(element.getTagName().equals("component-scan")) {
                    //处理注解扫描标签
                    String basePackage = element.getAttribute("base-package");
                    parseAnnotationBeanDefinitions(basePackage);
                } else if(element.getTagName().equals("bean")) {
                    parseXmlBeanDefinitions(element);
                }
            }
        }

    }

    private void parseXmlBeanDefinitions(Element element) {
        //处理bean标签
        String beanId = element.getAttribute("id");
        String className = element.getAttribute("class");
        //默认单例
        boolean isSingleton = true;
        if(element.hasAttribute("scope") && "prototype".equals(element.getAttribute("scope"))) {
            isSingleton = false;
        }

        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setSingleton(isSingleton);
        beanDefinition.setBeanClassName(className);
        processXmlBeanDefinitionProperty(element, beanDefinition);
        //beanDefinition放入容器
        this.getRegistry().put(beanId, beanDefinition);
    }

    /**
     * 根据xml中的bean标签封装beanDefinition
     *
     * @param element
     * @param beanDefinition
     */
    private void processXmlBeanDefinitionProperty(Element element, BeanDefinition beanDefinition) {
        NodeList property = element.getElementsByTagName("property");
        for (int i = 0; i < property.getLength(); i++) {
            Node item = property.item(i);
            if(item instanceof Element) {
                Element propertyEle = (Element) item;
                String name = propertyEle.getAttribute("name");
                String value = propertyEle.getAttribute("value");
                if(value != null && value.length() > 0) {
                    //读到value属性 进行值注入
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                } else {
                    //有ref属性，进行引用注入
                    String ref = propertyEle.getAttribute("ref");
                    if(ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException(
                                "Configuration problem: <property> element for property '"
                                        + name
                                        + "' must specify a ref or value");
                    }
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }

    /**
     * 根据注解封装BeanDefinition
     *
     * @param basePackage
     */
    private void parseAnnotationBeanDefinitions(String basePackage) {
        //先拿到basePackage下所有的Class
        Set<Class<?>> classes = PackageScaner.getClasses(basePackage);
        //然后对于每个Class 反射查看是否有注解
        for (Class clazz : classes) {
            processAnnotationBeanDefinition(clazz);
        }
    }

    private void processAnnotationBeanDefinition(Class<?> clazz) {
        if(clazz.isAnnotationPresent(Component.class)) {
            String beanName = clazz.getAnnotation(Component.class).name();
            if(beanName == null || beanName.length() == 0) {
                //默认name是ClassName首字母小写
                beanName = getDefaultBeanName(clazz);
            }
            String className = clazz.getName();
            boolean singleton = true;
            if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
                singleton = false;
            }

            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setBeanClassName(className);
            beanDefinition.setSingleton(singleton);
            //根据注解注入属性
            processAnnotationBeanDefinitionProperty(clazz, beanDefinition);
            //封装完成，放入容器
            this.getRegistry().put(beanName, beanDefinition);
        }
    }

    /**
     * 根据注解，获取到属性注入的元数据，封装在beanDefinition中
     *
     * @param clazz
     * @param beanDefinition
     */
    private void processAnnotationBeanDefinitionProperty(Class<?> clazz, BeanDefinition beanDefinition) {
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            String name = field.getName();
            if(field.isAnnotationPresent(Value.class)) {
                //值注入
                Value annotation = field.getAnnotation(Value.class);
                String value = annotation.value();
                if(value != null && value.length() > 0) {
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                }
            } else if(field.isAnnotationPresent(Autowired.class)) {
                //引用注入
                String beanId = field.getAnnotation(Autowired.class).beanId();
                if(beanId == null || beanId.length() == 0) {
                    beanId = getDefaultBeanName(field.getType());
                }
                BeanReference beanReference = new BeanReference(beanId);
                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
            }
        }
    }


    private String getDefaultBeanName(Class clazz) {
        String[] split = clazz.getName().split("\\.");
        String className = split[split.length - 1];
        return className.substring(0, 1).toLowerCase() + className.substring(1, className.length());
    }

}
